package com.lafeier.beefirm.parser;

import com.lafeier.beefirm.Beefirm;
import com.lafeier.beefirm.antlr4.BeefirmBaseVisitor;
import com.lafeier.beefirm.antlr4.BeefirmParser;
import com.lafeier.beefirm.tree.ClassDef;
import com.lafeier.beefirm.tree.FieldDef;
import com.lafeier.beefirm.tree.JavaCode;
import com.lafeier.beefirm.tree.MethodDef;
import org.antlr.v4.runtime.Token;
import org.antlr.v4.runtime.tree.TerminalNode;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;

public class BeefirmAstBuilder extends BeefirmBaseVisitor<Object> {

    @Override
    public ClassDef visitClassDef(BeefirmParser.ClassDefContext ctx) {

        ClassDef.ClassDefBuilder builder = ClassDef.builder();

        if (ctx.KW_ABSTRACT() != null) {
            builder.abstracted(true);
        }

        String kind = ctx.kind.getText();
        builder.kind(kind);

        String className = ctx.name.getText();
        builder.className(className);

        if (ctx.extendDef() != null) {
            String superClassName = (String) visitExtendDef(ctx.extendDef());
            builder.superClassName(superClassName);
        }

        if (ctx.implementDef() != null) {
            List<String> superInterfaces = (List<String>) visitImplementDef(ctx.implementDef());
            builder.superInterfaceList(superInterfaces);
        }

        List<String> comments = ctx.comments.stream().map(new Function<BeefirmParser.CommentDefContext, String>() {
            @Override
            public String apply(BeefirmParser.CommentDefContext commentDefContext) {
                return commentDefContext.getText();
            }
        }).collect(Collectors.toList());
        builder.comments(comments);

        //fields/methods
        if (ctx.classBody() != null) {
            BeefirmParser.ClassBodyContext classBodyContext = ctx.classBody();
            //field
            List<FieldDef> fields = classBodyContext.fields.stream()
                    .map(new Function<BeefirmParser.FieldDefContext, FieldDef>() {
                        @Override
                        public FieldDef apply(BeefirmParser.FieldDefContext fieldDefContext) {
                            return (FieldDef) visitFieldDef(fieldDefContext);
                        }
                    }).collect(Collectors.toList());
            builder.fieldList(fields);

            List<MethodDef> methods = classBodyContext.methods.stream().map(new Function<BeefirmParser.MethodDefContext, MethodDef>() {
                @Override
                public MethodDef apply(BeefirmParser.MethodDefContext methodDefContext) {
                    return (MethodDef) visitMethodDef(methodDefContext);
                }
            }).collect(Collectors.toList());
            builder.methodList(methods);
        }

        return builder.build();
    }

    @Override
    public Object visitImplementDef(BeefirmParser.ImplementDefContext ctx) {
        List<String> superInterfaces = ctx.superInterfaces.stream().map(new Function<Token, String>() {
            @Override
            public String apply(Token token) {
                return token.getText().trim();
            }
        }).collect(Collectors.toList());
        return superInterfaces;
    }

    @Override
    public JavaCode visitJavaCode(BeefirmParser.JavaCodeContext ctx) {
        List<ClassDef> classDefList = new ArrayList<>();
        ctx.classDef().forEach(new Consumer<BeefirmParser.ClassDefContext>() {
            @Override
            public void accept(BeefirmParser.ClassDefContext classDefContext) {
                ClassDef classDef = (ClassDef) visitClassDef(classDefContext);
                classDefList.add(classDef);
            }
        });
        JavaCode javaCode = JavaCode.builder()
                .classList(classDefList)
                .build();
        return javaCode;
    }

    @Override
    public FieldDef visitFieldDef(BeefirmParser.FieldDefContext ctx) {
        FieldDef.FieldDefBuilder builder = FieldDef.builder();

        if (ctx.VISIBILITY() != null) {
            String visibility = ctx.VISIBILITY().getText();
            builder.visibility(visibility);
        }
        if(ctx.KW_OVERRIDE()!=null){
            builder.override(true);
        }
        Optional.ofNullable(ctx.type).ifPresent(type->builder.type(type.getText()));

        String fieldName = ctx.fieldName.getText();
        builder.fieldName(fieldName);

        List<String> comments = ctx.comments.stream().map(new Function<BeefirmParser.CommentDefContext, String>() {
            @Override
            public String apply(BeefirmParser.CommentDefContext commentDefContext) {
                return commentDefContext.getText();
            }
        }).collect(Collectors.toList());
        builder.comments(comments);

        Optional.ofNullable(ctx.horizontal_line())
                .ifPresent(horizontal_lineContext ->
                        builder.horizontalLine(replaceCommentBoundary(horizontal_lineContext.getText())));

        return builder.build();
    }

    @Override
    public Object visitMethodDef(BeefirmParser.MethodDefContext ctx) {
        MethodDef.MethodDefBuilder builder = MethodDef.builder();

        if (ctx.VISIBILITY() != null) {
            String visibility = ctx.VISIBILITY().getText();
            builder.visibility(visibility);
        }
        if(ctx.KW_OVERRIDE()!=null){
            builder.override(true);
        }
        Optional.ofNullable(ctx.rtnType).ifPresent(rtn->builder.returnType(rtn.getText()));

        String methodName = ctx.methodName.getText();
        builder.methodName(methodName);

        if (ctx.paramSeq() != null) {
            BeefirmParser.ParamSeqContext paramSeqContext = ctx.paramSeq();
            List<MethodDef.MethodParam> methodParamList = paramSeqContext.params.stream().map(new Function<BeefirmParser.ParamDefContext, MethodDef.MethodParam>() {
                @Override
                public MethodDef.MethodParam apply(BeefirmParser.ParamDefContext paramDefContext) {
                    return (MethodDef.MethodParam) visitParamDef(paramDefContext);
                }
            }).collect(Collectors.toList());
            builder.paramList(methodParamList);
        }

        List<String> comments = ctx.comments.stream().map(new Function<BeefirmParser.CommentDefContext, String>() {
            @Override
            public String apply(BeefirmParser.CommentDefContext commentDefContext) {
                return commentDefContext.getText();
            }
        }).collect(Collectors.toList());
        builder.comments(comments);

        Optional.ofNullable(ctx.horizontal_line())
                .ifPresent(horizontal_lineContext ->
                        builder.horizontalLine(replaceCommentBoundary(horizontal_lineContext.getText())));

        return builder.build();
    }

    @Override
    public Object visitParamDef(BeefirmParser.ParamDefContext ctx) {
        MethodDef.MethodParam.MethodParamBuilder builder = MethodDef.MethodParam.builder();

        Optional.ofNullable(ctx.type).ifPresent(type->builder.paramType(type.getText()));

        String paramName = ctx.paramName.getText();
        builder.paramName(paramName);

        List<String> comments = ctx.comments.stream().map(new Function<Token, String>() {
            @Override
            public String apply(Token token) {
                return token.getText();
            }
        }).collect(Collectors.toList());
        builder.comments(comments);

        return builder.build();
    }

    @Override
    public String visitExtendDef(BeefirmParser.ExtendDefContext ctx) {
        return ctx.superClass.getText();
    }
    private String replaceCommentBoundary(String content){
        return content.substring(2,content.length()-2);
    }
}
